/**
 * Copyright 2011 Nikolche Mihajlovski
 *
 * This file is part of JAnnocessor.
 *
 * JAnnocessor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAnnocessor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAnnocessor.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.jannocessor.service.imports;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.jannocessor.service.api.ImportOrganizer;

public class ImportOrganizerImpl implements ImportOrganizer {

	private static final String JAVA_LANG = "java.lang.";

	private Set<String> importedSimple = new HashSet<String>();

	private Set<String> importedFull = new HashSet<String>();

	@Override
	public String[] getTypeImports(String type) {
		List<String> imports = new ArrayList<String>();
		calculateTypeImports(type, imports);
		return imports.toArray(new String[imports.size()]);
	}

	private void calculateTypeImports(String type, List<String> imports) {
		ParsedTypeName parsedName = parseType(type);
		String simpleName = parsedName.getClassName();
		String genericType = parsedName.getGenericType();

		if (!parsedName.isSimple() && !genericType.startsWith(JAVA_LANG)) {
			if (!importedSimple.contains(simpleName)) {
				importedSimple.add(simpleName);
				importedFull.add(genericType);
				imports.add(genericType);
			}
		}

		for (ParsedTypeNameParam generic : parsedName.getParams()) {
			calculateTypeImports(generic.getType(), imports);
		}
	}

	@Override
	public String getTypeUsage(String type) {
		ParsedTypeName parsedName = parseType(type);
		String simpleName = parsedName.getClassName();
		String genericType = parsedName.getGenericType();

		if (!parsedName.isSimple()) {
			if (genericType.startsWith(JAVA_LANG)
					|| importedFull.contains(genericType)) {
				return simpleName + renderParams(parsedName.getParams())
						+ parsedName.getArrayPart();
			} else {
				return type + renderParams(parsedName.getParams());
			}
		} else {
			return type; // a simple type
		}
	}

	private String renderParams(List<ParsedTypeNameParam> params) {
		if (!params.isEmpty()) {
			StringBuilder sb = new StringBuilder();
			sb.append("<");

			for (Iterator<ParsedTypeNameParam> it = params.iterator(); it
					.hasNext();) {
				ParsedTypeNameParam type = (ParsedTypeNameParam) it.next();

				if (StringUtils.isNotEmpty(type.getWildcard())) {
					sb.append(type.getWildcard());
					sb.append(" ");
				}

				sb.append(getTypeUsage(type.getType()));

				if (it.hasNext()) {
					sb.append(", ");
				}
			}

			sb.append(">");
			return sb.toString();
		} else {
			return "";
		}
	}

	private ParsedTypeName parseType(String type) {
		String regex = "^(?:([^<>]+)\\.)?([^.<>]+?)(?:\\s?<(.*)>)?((?:\\[\\])*)?$";
		Matcher matcher = Pattern.compile(regex).matcher(type);
		if (matcher.matches()) {
			List<ParsedTypeNameParam> params = extractParams(matcher.group(3));
			int arrayDimensions = calculateArrayDimensions(matcher.group(4));
			return new ParsedTypeName(matcher.group(1), matcher.group(2),
					params, arrayDimensions);
		} else {
			throw new IllegalArgumentException("Cannot parse type!");
		}
	}

	private int calculateArrayDimensions(String dim) {
		return StringUtils.isNotEmpty(dim) ? dim.length() / 2 : 0;
	}

	private List<ParsedTypeNameParam> extractParams(String params) {
		List<ParsedTypeNameParam> parts = new ArrayList<ParsedTypeNameParam>();

		if (StringUtils.isNotEmpty(params)) {

			StringBuffer sb = new StringBuffer(params);
			Pattern pattern = Pattern.compile("<[^<>]+?>");

			Matcher m = pattern.matcher(sb.toString());
			while (m.find()) {
				for (int i = m.start(); i < m.end(); i++) {
					sb.setCharAt(i, ' ');
				}
				m = pattern.matcher(sb.toString());
			}
			String projection = sb.toString();

			int from = 0;
			int pos = projection.indexOf(',');

			while (pos > 0) {
				String part = params.substring(from, pos).trim();
				parts.add(typeParam(part));

				from = pos + 1;
				pos = projection.indexOf(',', pos + 1);
			}

			String part = params.substring(from).trim();
			parts.add(typeParam(part));
		}

		return parts;
	}

	private ParsedTypeNameParam typeParam(String type) {
		String regex = "^((?:\\?|[\\w$]+)\\s(?:extends|super))\\s+(.*)$";
		Matcher matcher = Pattern.compile(regex).matcher(type);
		if (matcher.matches()) {
			return new ParsedTypeNameParam(matcher.group(1), matcher.group(2));
		} else {
			return new ParsedTypeNameParam("", type);
		}

	}

}